﻿#region Copyright (c) 2011, thinktecture (http://www.thinktecture.com)

/*
   thinktecture.UIAnnotations was developed by Jörg Neumann.
   Copyright (c) 2011, thinktecture (http://www.thinktecture.com).
   All rights reserved, comes as-is and without any warranty. Use of this
   source file is governed by the license which is contained in LICENSE.TXT 
   in the distribution.
*/

#endregion

using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Controls.Primitives;

namespace Thinktecture.UIAnnotations
{
    public sealed class SmartTagAdorner : Adorner, IDisposable
    {
        private bool _show;
        private SmartTagControl _child;
        private UIElement _adornedElement;
        private FrameworkElement _focusElement;
        private ContextMenu _menu;
        private bool _focusHandlerAdded = false;

        public SmartTagAdorner(UIElement adornedElement, ControlTemplate adornerTemplate, ContextMenu contextMenu)
            : base(adornedElement)
        {
            this.UseLayoutRounding = true;
            this.SnapsToDevicePixels = true;
            _menu = contextMenu;
            _child = new SmartTagControl(contextMenu);
            _child.Focusable = false;
            _child.Template = adornerTemplate;
            _child.AddHandler(Button.ClickEvent, new RoutedEventHandler(SmartTag_Click));
            base.AddVisualChild(_child);

            _adornedElement = adornedElement;
            _focusElement = adornedElement as FrameworkElement;
            if (_focusElement != null)
            {
                this.AddFocusHandler();
            }

            this.Visibility = Visibility.Collapsed;
            this.Focusable = false;
            _show = true;
        }
        
        private void SmartTag_Click(object sender, RoutedEventArgs e)
        {
            Button bt = sender as Button;
            Button ctl = bt.Template.FindName("smartTagButton", bt) as Button;
            if (ctl != null && _menu != null)
            {
                ctl.Style = ctl.FindResource("SmartTagContextMenuStyle") as Style;
                ContextMenu mnu = _menu;
                mnu.Focusable = false;
                mnu.IsHitTestVisible = true;
                mnu.IsOpen = true;
                mnu.StaysOpen = true;
                mnu.Placement = PlacementMode.Bottom;
                mnu.PlacementTarget = bt;
                mnu.AddHandler(ContextMenu.ClosedEvent, new RoutedEventHandler(mnu_Closed));
                //ContextMenuService.SetPlacement(mnu, PlacementMode.Bottom);
            }
        }

        private void mnu_Closed(object sender, RoutedEventArgs e)
        {
            ContextMenu mnu = sender as ContextMenu;
            mnu.RemoveHandler(ContextMenu.ClosedEvent, new RoutedEventHandler(mnu_Closed));
            Button bt = mnu.PlacementTarget as Button;
            Button ctl = bt.Template.FindName("smartTagButton", bt) as Button;
            ctl.Style = bt.FindResource("SmartTagStyle") as Style;
        }

        internal void AddFocusHandler()
        {
            TextBox txt = GetTextBox(_focusElement);
            if (txt != null && !_focusHandlerAdded)
            {
                _focusElement.AddHandler(UIElement.GotFocusEvent, new RoutedEventHandler(this.OnGotFocus));
                _focusElement.AddHandler(UIElement.LostFocusEvent, new RoutedEventHandler(this.OnLostFocus));
                _focusHandlerAdded = true;
            }
            else if (txt == null && !(_focusElement is ComboBox) && !(_focusElement is DatePicker))
            {
                _focusElement.AddHandler(UIElement.MouseEnterEvent, new RoutedEventHandler(this.OnMouseEnter));
                _focusElement.AddHandler(UIElement.MouseLeaveEvent, new RoutedEventHandler(this.OnMouseLeave));
                _focusHandlerAdded = true;
            }
        }

        #region Event Handling

        private void OnGotFocus(object sender, RoutedEventArgs e)
        {
            if (_show)
                this.Visibility = Visibility.Visible;
        }

        private void OnLostFocus(object sender, RoutedEventArgs e)
        {
            object obj = Mouse.DirectlyOver;
            if (obj is Image && ((Image)obj).Name == "PART_Image")
            {
            }
            else
                this.Visibility = Visibility.Hidden;
        }

        private void OnMouseEnter(object sender, RoutedEventArgs e)
        {
            if (_show)
                this.Visibility = Visibility.Visible;
        }

        private void OnMouseLeave(object sender, RoutedEventArgs e)
        {
            FrameworkElement obj2 = Mouse.Captured as FrameworkElement;
            FrameworkElement obj = Mouse.DirectlyOver as FrameworkElement;
            if ((obj2 != null) || (obj != null))
            {
                if (obj.Name == "PART_Image" || obj.Name == "PART_Border" || obj.Name == "PART_ChildBorder")
                {
                }
                else
                    this.Visibility = Visibility.Hidden;
            }
            else
                this.Visibility = Visibility.Hidden;
        }

        #endregion

        #region Private Functions

        private TextBox GetTextBox(object element)
        {
            if (element is TextBox)
            {
                return (TextBox)element;
            }
            else if (element is ComboBox)
            {
                ComboBox comboBox = element as ComboBox;
                if (comboBox.Template != null)
                    return comboBox.Template.FindName("PART_EditableTextBox", comboBox) as TextBox;
                else
                    System.Diagnostics.Debug.WriteLine("Can't find ComboBox editor!");
            }
            else if (element is DatePicker)
            {
                DatePicker picker = element as DatePicker;
                if (picker.Template != null)
                    return picker.Template.FindName("PART_TextBox", picker) as TextBox;
                else
                    System.Diagnostics.Debug.WriteLine("Can't find DatePicker editor!");
            }
            return null;
        }

        private childItem FindVisualChild<childItem>(DependencyObject obj)
                where childItem : DependencyObject
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                if (child != null && child is childItem)
                {
                    return (childItem)child;
                }
                else
                {
                    childItem childOfChild = FindVisualChild<childItem>(child);
                    if (childOfChild != null)
                        return childOfChild;
                }
            }
            return null;
        }

        #endregion

        #region Public Properties

        public bool Show
        {
            get { return _show; }
            set
            {
                _show = value;
                this.Visibility = (_show) ? Visibility.Visible : Visibility.Collapsed;
                this.InvalidateVisual();
            }
        }

        public ContextMenu Menu
        {
            get { return _menu; }
            set
            {
                if (_child != null)
                    _child.Menu = value;
                _menu = value;
            }
        }

        #endregion

        #region Overrides

        protected override Size ArrangeOverride(Size size)
        {
            Size size2 = base.ArrangeOverride(size);
            Size size3 = new Size(size2.Width, size2.Height - 4);
            if (_child != null)
            {
                if (_adornedElement is TextBox || _adornedElement is ComboBox || _adornedElement is DatePicker)
                    _child.Arrange(new Rect(new Point(2, -2), size2));
                else
                    _child.Arrange(new Rect(new Point(1, -20), size2));
            }
            return size2;
        }
        
        protected override Visual GetVisualChild(int index)
        {
            if ((_child == null) || (index != 0))
                throw new ArgumentOutOfRangeException("index");
            return _child;
        }

        protected override Size MeasureOverride(Size constraint)
        {
            _child.Measure(constraint);
            return _child.DesiredSize;
        }

        protected override int VisualChildrenCount
        {
            get
            {
                if (_child == null)
                    return 0;
                return 1;
            }
        }
        
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            if (_focusElement != null)
            {
                _focusElement.RemoveHandler(UIElement.MouseEnterEvent, new RoutedEventHandler(this.OnMouseEnter));
                _focusElement.RemoveHandler(UIElement.MouseLeaveEvent, new RoutedEventHandler(this.OnMouseLeave));
                _focusElement.RemoveHandler(UIElement.GotFocusEvent, new RoutedEventHandler(this.OnGotFocus));
                _focusElement.RemoveHandler(UIElement.LostFocusEvent, new RoutedEventHandler(this.OnLostFocus));
                _focusElement = null;
            }
            if (_adornedElement != null)
                _adornedElement = null;
            if (_child != null)
            {
                _child.ClearValue(Control.ContextMenuProperty);
                _child.RemoveHandler(Button.ClickEvent, new RoutedEventHandler(SmartTag_Click));
                base.RemoveVisualChild(_child);
                _child.ClearValue(Control.TemplateProperty);
                _child = null;
            }
            if (_menu != null)
            {
                _menu = null;
            }
        }

        #endregion

        internal sealed class SmartTagControl : Button
        {
            public SmartTagControl(ContextMenu menu)
            {
                this.UseLayoutRounding = true;
                this.SnapsToDevicePixels = true;
                this.Menu = menu;
            }
            public ContextMenu Menu { get; set; }
        }
    }
}
